package editor.animation;

import animation.Animation;
import animation.AnimationStateMachine;
import animation.AnimationTree;
import com.crunch.math.Vector2f;
import com.crunch.math.Vector4f;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.*;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;

public class AnimationTreePanel extends EditorPanel {
	private JPanel treeControlsPanel;
	private JButton addNodeButton;
	private JButton removeNodeButton;
	private JTextField nameField;
	private JTree nodeTree;
	private DefaultTreeModel model;
	private DnDJTree dnDJTree;

	private static class NodeFlavor {}
	private static final Class FLAVOR_CLASS = NodeFlavor.class;
	private static final String FLAVOR_STRING = "TreeNode";

	private JPanel nodeControlsPanel;
	private JTextField typeField;
	private JPopupMenu typePopupMenu;
	private JLabel animationLabel;
	private JTextField animationField;
	private JCheckBox wrapCheckbox;

	private boolean performCallbacks = true;

	@Override
	public String getTabName() {
		return "Animation Tree";
	}

	public AnimationTreePanel(AnimationEditor editor) {
		this.editor = editor;
		GroupLayout topLayout = new GroupLayout(this);
		setLayout(topLayout);
		topLayout.setAutoCreateGaps(true);
		topLayout.setAutoCreateContainerGaps(true);

		treeControlsPanel = new JPanel();
		treeControlsPanel.setBorder(BorderFactory.createEtchedBorder());
		{
			GroupLayout layout = new GroupLayout(treeControlsPanel);
			treeControlsPanel.setLayout(layout);
			layout.setAutoCreateGaps(true);
			layout.setAutoCreateContainerGaps(true);

			addNodeButton = new JButton("Add node");
			addNodeButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					synchronized (AnimationTreePanel.this.editor) {
						addNode();
					}
				}
			});
			removeNodeButton = new JButton("Remove node");
			removeNodeButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					synchronized (AnimationTreePanel.this.editor) {
						removeNode();
					}
				}
			});

			JLabel nameLabel = new JLabel("Name:");
			nameField = new JTextField();
			nameField.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					synchronized (AnimationTreePanel.this.editor) {
						if (!performCallbacks) {
							return;
						}
						storeNodeProperties();
					}
				}
			});
			nameField.addFocusListener(new FocusListener() {
				@Override
				public void focusGained(FocusEvent e) {
				}

				@Override
				public void focusLost(FocusEvent e) {
					synchronized (AnimationTreePanel.this.editor) {
						if (!performCallbacks) {
							return;
						}
						storeNodeProperties();
					}
				}
			});
			nameLabel.setLabelFor(nameField);

			nodeTree = new JTree((ColoredNode) null);
			nodeTree.setCellRenderer(new ColoredNodeRenderer());
			model = (DefaultTreeModel) nodeTree.getModel();
			nodeTree.setShowsRootHandles(true);
			nodeTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
			nodeTree.addTreeSelectionListener(new TreeSelectionListener() {
				@Override
				public void valueChanged(TreeSelectionEvent e) {
					synchronized (AnimationTreePanel.this.editor) {
						if (!performCallbacks) {
							return;
						}

						TreePath path = e.getNewLeadSelectionPath();
						ColoredNode node = (path == null) ?
								null : (ColoredNode) path.getLastPathComponent();
						String sel;
						if (node == null) {
							sel = null;
						} else {
							sel = node.toString();
						}

						storeNodeProperties();
						nodeSelection = sel;
						setNodeControlsEnabled(sel != null);
						if (sel != null) {
							loadNodeProperties();
						}
					}
				}
			});
			JScrollPane nodeTreeScroll = new JScrollPane(nodeTree);

			dnDJTree = new DnDJTree(nodeTree, FLAVOR_CLASS, FLAVOR_STRING) {
				class TransferableNode implements Transferable {
					public TransferableNode(Object data) {
						this.data = data;
					}

					private Object data;

					@Override
					public DataFlavor[] getTransferDataFlavors() {
						return new DataFlavor[] { new DataFlavor(FLAVOR_CLASS, FLAVOR_STRING) };
					}

					@Override
					public boolean isDataFlavorSupported(DataFlavor flavor) {
						return flavor.getRepresentationClass() == FLAVOR_CLASS;
					}

					@Override
					public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
						if (isDataFlavorSupported(flavor)) {
							return data;
						} else {
							throw new UnsupportedFlavorException(flavor);
						}
					}
				}

				@Override
				public Transferable getTransferable(Object node) {
					return new TransferableNode(node);
				}

				@Override
				public Object getNode(Transferable transferable) {
					try {
						return transferable.getTransferData(new DataFlavor(FLAVOR_CLASS, FLAVOR_STRING));
					} catch (UnsupportedFlavorException e) {
						e.printStackTrace();
					} catch (IOException e) {
						e.printStackTrace();
					}
					return null;
				}

				@Override
				public void moveNodeRequest(Object draggedNode, Object droppedNode, Where where) {
					synchronized (AnimationTreePanel.this.editor) {
						moveNode((ColoredNode) draggedNode, (ColoredNode) droppedNode, where);
					}
				}
			};

			layout.setHorizontalGroup(layout.createParallelGroup()
					.addGroup(layout.createSequentialGroup()
							.addComponent(addNodeButton)
							.addComponent(removeNodeButton)
							.addComponent(nameLabel)
							.addComponent(nameField))
					.addComponent(nodeTreeScroll));
			layout.setVerticalGroup(layout.createSequentialGroup()
					.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
							.addComponent(addNodeButton)
							.addComponent(removeNodeButton)
							.addComponent(nameLabel)
							.addComponent(nameField))
					.addComponent(nodeTreeScroll));
		}

		nodeControlsPanel = new JPanel();
		nodeControlsPanel.setBorder(BorderFactory.createEtchedBorder());
		{
			GroupLayout layout = new GroupLayout(nodeControlsPanel);
			nodeControlsPanel.setLayout(layout);
			layout.setAutoCreateGaps(true);
			layout.setAutoCreateContainerGaps(true);

			JLabel typeLabel = new JLabel("Type:");
			typeField = new JTextField();
			typeField.setEditable(false);
			typeLabel.setLabelFor(typeField);

			typePopupMenu = new JPopupMenu();
			for (int i = 0; i < AnimationTree.Node.Type.values().length; ++i) {
				JMenuItem typeItem = new JMenuItem(AnimationTree.Node.Type.values()[i].toString());
				final int index = i;
				typeItem.addActionListener(new ActionListener() {
					@Override
					public void actionPerformed(ActionEvent e) {
						synchronized (AnimationTreePanel.this.editor) {
							setNodeType(AnimationTree.Node.Type.values()[index]);
						}
					}
				});
				typePopupMenu.add(typeItem);
			}

			typeField.addMouseListener(new MouseListener() {
				@Override
				public void mouseClicked(MouseEvent e) {
					if (typeField.isEnabled() && e.getButton() == MouseEvent.BUTTON1) {
						typePopupMenu.show(e.getComponent(), e.getX(), e.getY());
					}
				}

				public void mousePressed(MouseEvent e) {}
				public void mouseReleased(MouseEvent e) {}
				public void mouseEntered(MouseEvent e) {}
				public void mouseExited(MouseEvent e) {}
			});

			animationLabel = new JLabel("Animation:");
			animationField = new JTextField();
			animationField.setEditable(false);
			animationField.addMouseListener(new MouseListener() {
				@Override
				public void mouseClicked(MouseEvent e) {
					synchronized (AnimationTreePanel.this.editor) {
						// show list of animations to choose from
						Object[] list = new Object[AnimationTreePanel.this.editor.animationNames.size()];
						for (int i = 0; i < list.length; ++i) {
							list[i] = AnimationTreePanel.this.editor.animationNames.get(i);
						}
						String animation = (String) JOptionPane.showInputDialog(
								AnimationTreePanel.this,
								"Choose animation:",
								"Animation Selector",
								JOptionPane.PLAIN_MESSAGE,
								null,
								list,
								null);
						if (animation != null) {
							animationField.setText(animation);
							storeNodeProperties();
						}
					}
				}

				public void mousePressed(MouseEvent e) {}
				public void mouseReleased(MouseEvent e) {}
				public void mouseEntered(MouseEvent e) {}
				public void mouseExited(MouseEvent e) {}
			});
			animationLabel.setLabelFor(animationField);

			wrapCheckbox = new JCheckBox("Wrap");
			wrapCheckbox.addChangeListener(new ChangeListener() {
				@Override
				public void stateChanged(ChangeEvent e) {
					synchronized (AnimationTreePanel.this.editor) {
						if (!performCallbacks) {
							return;
						}
						storeNodeProperties();
					}
				}
			});

			layout.setHorizontalGroup(layout.createParallelGroup()
					.addGroup(layout.createSequentialGroup()
							.addComponent(typeLabel)
							.addComponent(typeField))
					.addGroup(layout.createSequentialGroup()
							.addComponent(animationLabel)
							.addComponent(animationField))
					.addComponent(wrapCheckbox));
			layout.setVerticalGroup(layout.createSequentialGroup()
					.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
							.addComponent(typeLabel)
							.addComponent(typeField))
					.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
							.addComponent(animationLabel)
							.addComponent(animationField))
					.addComponent(wrapCheckbox));
		}

		topLayout.setHorizontalGroup(topLayout.createParallelGroup(GroupLayout.Alignment.CENTER)
				.addComponent(treeControlsPanel)
				.addComponent(nodeControlsPanel));
		topLayout.setVerticalGroup(topLayout.createSequentialGroup()
				.addComponent(treeControlsPanel)
				.addComponent(nodeControlsPanel));
	}

	@Override
	public void select() {
		boolean tempPerformCallbacks = performCallbacks;
		performCallbacks = false;
		try {
			// clear the tree and rebuild it up
			nodeTree.clearSelection();
			model.setRoot(null);
			model.reload();

			addTreeNodes(editor.animationTree.getRoot(), null);
		} finally {
			performCallbacks = tempPerformCallbacks;
		}

		// expand the whole tree - note, this is probably n^2 due to the getRowCount() call in the loop
		for (int i = 0; i < nodeTree.getRowCount(); ++i) {
			nodeTree.expandRow(i);
		}

		nodeSelection = null;
		setNodeControlsEnabled(false);

		constructCharacterInstance();
	}

	@Override
	public void deselect() {}

	@Override
	public void close() {}

	private void addTreeNodes(AnimationTree.Node node, ColoredNode parent) {
		ColoredNode treeNode = new ColoredNode(node.getName());
		validateNode(treeNode);
		if (parent == null) {
			model.setRoot(treeNode);
		} else {
			model.insertNodeInto(treeNode, parent, parent.getChildCount());
		}

		switch (node.getType()) {
			case ANIMATION:
			{
				// do nothing - no children
				break;
			}
			case SELECT:
			{
				AnimationTree.SelectNode n = (AnimationTree.SelectNode) node;
				for (int i = 0; i < n.getChildCount(); ++i) {
					addTreeNodes(n.getChild(i), treeNode);
				}
				break;
			}
			case BLEND:
			{
				AnimationTree.BlendNode n = (AnimationTree.BlendNode) node;
				for (int i = 0; i < n.getChildCount(); ++i) {
					addTreeNodes(n.getChild(i), treeNode);
				}
				break;
			}
			case SEQUENCE:
			{
				AnimationTree.SequenceNode n = (AnimationTree.SequenceNode) node;
				for (int i = 0; i < n.getChildCount(); ++i) {
					addTreeNodes(n.getChild(i), treeNode);
				}
				break;
			}
			default:
				throw new IllegalArgumentException("Invalid node type " + node.getType());
		}
	}

	private String nodeSelection;   // name of node selected within tree

	private void storeNodeProperties() {
		if (nodeSelection == null) {
			return;
		}
		boolean tempPerformCallbacks = performCallbacks;
		performCallbacks = false;
		try {
			// name
			String name = nameField.getText();
			if (name.isEmpty() || editor.animationTree.getNode(name) != null) {
				// revert to current node name
				nameField.setText(nodeSelection);
			} else {
				// set the node's name
				String oldName = nodeSelection;
				editor.animationTree.getNode(oldName).setName(name);
				ColoredNode root = (ColoredNode) model.getRoot();
				@SuppressWarnings("unchecked")
				Enumeration<ColoredNode> enumeration = root.preorderEnumeration();
				while (enumeration.hasMoreElements()) {
					ColoredNode node = enumeration.nextElement();
					if (node.toString().equals(oldName)) {
						model.valueForPathChanged(new TreePath(node.getPath()), name);
						nodeTree.repaint();
						break;
					}
				}
				nodeSelection = name;
			}

			// store specific type - we should never have to change type in this function
			AnimationTree.Node node = editor.animationTree.getNode(nodeSelection);
			assert(node.getType().toString().equals(typeField.getText()));

			switch (node.getType()) {
				case ANIMATION:
					((AnimationTree.AnimationNode) node).setAnimation(animationField.getText());
					validateAnimationField();
					break;
				case SELECT:
					break;
				case BLEND:
					((AnimationTree.BlendNode) node).setWrap(wrapCheckbox.isSelected());
					break;
				case SEQUENCE:
					break;
				default:
					throw new IllegalArgumentException("Invalid node type " + node.getType());
			}

			// find and validate the node
			ColoredNode root = (ColoredNode) model.getRoot();
			@SuppressWarnings("unchecked")
			Enumeration<ColoredNode> enumeration = root.preorderEnumeration();
			while (enumeration.hasMoreElements()) {
				ColoredNode next = enumeration.nextElement();
				if (next.toString().equals(name)) {
					validateNode(next);
					break;
				}
			}
		} finally {
			performCallbacks = tempPerformCallbacks;
		}
	}

	private void loadNodeProperties() {
		boolean tempPerformCallbacks = performCallbacks;
		performCallbacks = false;
		try {
			AnimationTree.Node node = editor.animationTree.getNode(nodeSelection);
			nameField.setText(node.getName());
			typeField.setText(node.getType().toString());

			switch (node.getType()) {
				case ANIMATION:
					animationField.setText(((AnimationTree.AnimationNode) node).getAnimation());
					validateAnimationField();
					break;
				case SELECT:
					break;
				case BLEND:
					wrapCheckbox.setSelected(((AnimationTree.BlendNode) node).getWrap());
					break;
				case SEQUENCE:
					break;
				default:
					throw new IllegalArgumentException("Invalid node type " + node.getType());
			}
		} finally {
			performCallbacks = tempPerformCallbacks;
		}
	}

	private void setNodeControlsEnabled(boolean e) {
		boolean tempPerformCallbacks = performCallbacks;
		performCallbacks = false;
		try {
			nameField.setEnabled(e);
			typeField.setEnabled(e);
			if (nodeSelection != null && editor.animationTree.getRoot().getName().equals(nodeSelection)) {
				removeNodeButton.setEnabled(false);
			} else {
				removeNodeButton.setEnabled(e);
			}

			if (nodeSelection == null) {
				addNodeButton.setEnabled(false);
				animationLabel.setVisible(false);
				animationField.setVisible(false);
				wrapCheckbox.setVisible(false);
			} else {
				AnimationTree.Node node = editor.animationTree.getNode(nodeSelection);
				addNodeButton.setEnabled(node.getType() != AnimationTree.Node.Type.ANIMATION);

				animationLabel.setVisible(node.getType() == AnimationTree.Node.Type.ANIMATION);
				animationField.setVisible(node.getType() == AnimationTree.Node.Type.ANIMATION);

				wrapCheckbox.setVisible(node.getType() == AnimationTree.Node.Type.BLEND);
			}
		} finally {
			performCallbacks = tempPerformCallbacks;
		}
	}

	private void addNode() {
		int val = 0;
		// find unused node name
		boolean used = true;
		while (used) {
			if (editor.animationTree.getNode("node" + val) == null) {
				used = false;
			} else {
				++val;
			}
		}

		String name = "node" + val;
		ColoredNode newNode = new ColoredNode(name);

		// add an animation node by default
		AnimationTree.AnimationNode animNode = new AnimationTree.AnimationNode(name);
		animNode.setAnimation("");

		assert(nodeSelection != null);
		// add to child
		ColoredNode node = (ColoredNode) nodeTree.getSelectionPath().getLastPathComponent();
		String parent = node.toString();
		AnimationTree.Node parentNode = editor.animationTree.getNode(parent);
		switch (parentNode.getType()) {
			case ANIMATION:
				assert(false);
				break;
			case SELECT:
			{
				AnimationTree.SelectNode n = (AnimationTree.SelectNode) parentNode;
				n.addChild(n.getChildCount(), animNode);
				break;
			}
			case BLEND:
			{
				AnimationTree.BlendNode n = (AnimationTree.BlendNode) parentNode;
				n.addChild(n.getChildCount(), animNode);
				break;
			}
			case SEQUENCE:
			{
				AnimationTree.SequenceNode n = (AnimationTree.SequenceNode) parentNode;
				n.addChild(n.getChildCount(), animNode);
				break;
			}
		}

		model.insertNodeInto(newNode, node, node.getChildCount());
		nodeTree.setSelectionPath(new TreePath(newNode.getPath()));
		validateNode(newNode);
	}

	private void removeNode() {
		String sel = nodeSelection;

		ColoredNode treeNode = (ColoredNode) nodeTree.getSelectionPath().getLastPathComponent();
		nodeTree.clearSelection();

		// obtain the parent and remove this child
		AnimationTree.Node node = editor.animationTree.getNode(sel);
		AnimationTree.Node parent = node.getParent();
		int indexInParent = -1;
		for (int c = 0; c < parent.getChildCount(); ++c) {
			if (parent.getChild(c) == node) {
				indexInParent = c;
				break;
			}
		}
		assert(indexInParent >= 0);
		parent.removeChild(indexInParent);
		((DefaultTreeModel) nodeTree.getModel()).removeNodeFromParent(treeNode);
	}

	private void setNodeType(AnimationTree.Node.Type type) {
		AnimationTree.Node node = editor.animationTree.getNode(nodeSelection);
		if (type == node.getType()) {
			return; // no change
		}
		int childCount = node.getChildCount();
		if (type == AnimationTree.Node.Type.ANIMATION && childCount != 0) {
			// warn user about deleting child nodes
			int result = JOptionPane.showConfirmDialog(this,
					"Changing to ANIMATION node will delete child nodes. Continue?",
					"Change node type?",
					JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
			if (result == JOptionPane.NO_OPTION) {
				return;
			}
		}

		AnimationTree.Node newNode;
		// change the type here
		switch (type) {
			case ANIMATION:
			{
				AnimationTree.AnimationNode n = new AnimationTree.AnimationNode(node.getName());
				n.setAnimation("");
				newNode = n;
				break;
			}
			case SELECT:
			{
				newNode = new AnimationTree.SelectNode(node.getName());
				break;
			}
			case BLEND:
			{
				newNode = new AnimationTree.BlendNode(node.getName());
				break;
			}
			case SEQUENCE:
			{
				newNode = new AnimationTree.SequenceNode(node.getName());
				break;
			}
			default:
				throw new IllegalArgumentException("Invalid node type " + node.getType());
		}

		ColoredNode treeNode = (ColoredNode) nodeTree.getSelectionPath().getLastPathComponent();

		// remove old node from tree
		// keep reference to it so we can copy the hierarchy
		if (node.getParent() == null) {
			editor.animationTree.setRoot(newNode);
		} else {
			// obtain the parent and remove this child
			AnimationTree.Node parent = node.getParent();
			int indexInParent = -1;
			for (int c = 0; c < parent.getChildCount(); ++c) {
				if (parent.getChild(c) == node) {
					indexInParent = c;
					break;
				}
			}
			assert(indexInParent >= 0);
			parent.removeChild(indexInParent);
			parent.addChild(indexInParent, newNode);
		}

		// if the node isn't ANIMATION, copy the hierarchy
		if (newNode.getType() != AnimationTree.Node.Type.ANIMATION) {
			while (node.getChildCount() > 0) {
				AnimationTree.Node child = node.getChild(0);
				node.removeChild(0);
				newNode.addChild(newNode.getChildCount(), child);
			}
		} else {
			// remove the hierarchy in the tree
			while (treeNode.getChildCount() > 0) {
				model.removeNodeFromParent((ColoredNode) treeNode.getLastChild());
			}
		}

		validateNode(treeNode);

		// now that everything is set, load the properties to refresh the GUI
		loadNodeProperties();
		setNodeControlsEnabled(true); // to refresh the "add" button
	}

	private void moveNode(ColoredNode draggedTreeNode, ColoredNode droppedTreeNode, DnDJTree.Where where) {
		AnimationTree.Node draggedNode = editor.animationTree.getNode(draggedTreeNode.toString());
		AnimationTree.Node droppedNode = editor.animationTree.getNode(droppedTreeNode.toString());
		// first determine where to put draggedNode based on WHERE
		AnimationTree.Node parentNode;
		ColoredNode parentTreeNode;
		int childPos = -1;
		switch (where) {
			case ABOVE:
				if (droppedNode.getParent() == null) {
					return; // root - can't drag here
				}
				parentNode = droppedNode.getParent();
				parentTreeNode = (ColoredNode) droppedTreeNode.getParent();
				for (int c = 0; c < parentNode.getChildCount(); ++c) {
					if (parentNode.getChild(c) == droppedNode) {
						childPos = c; // place before droppedNode
						break;
					}
				}
				break;
			case BELOW:
				if (droppedNode.getParent() == null) {
					return; // root - can't drag here
				}
				parentNode = droppedNode.getParent();
				parentTreeNode = (ColoredNode) droppedTreeNode.getParent();
				for (int c = 0; c < parentNode.getChildCount(); ++c) {
					if (parentNode.getChild(c) == droppedNode) {
						childPos = c+1; // place after droppedNode
						break;
					}
				}
				break;
			case CHILD:
				parentNode = droppedNode;
				parentTreeNode = droppedTreeNode;
				childPos = droppedNode.getChildCount();
				break;
			default:
				throw new IllegalArgumentException("Invalid where " + where);
		}

		assert(childPos >= 0);

		// make sure the parent node can support children
		if (parentNode.getType() == AnimationTree.Node.Type.ANIMATION) {
			return;
		}

		// make sure the new parent isn't the node itself or a child of it
		AnimationTree.Node next = parentNode;
		while (next != null) {
			if (next == draggedNode) {
				return;
			}
			next = next.getParent();
		}

		AnimationTree.Node oldParentNode = draggedNode.getParent();
		if (oldParentNode == null) {
			return; // can't move root
		}
		int oldPos = -1;
		for (int c = 0; c < oldParentNode.getChildCount(); ++c) {
			if (oldParentNode.getChild(c) == draggedNode) {
				oldPos = c;
				break;
			}
		}
		assert(oldPos >= 0);

		// remove draggedNode from its current parent and insert under its new parent
		// we need to be a bit careful with indices if the parents are the same
		int addPos = childPos;
		if (parentNode == oldParentNode && oldPos < childPos) {
			--addPos;
		}
		oldParentNode.removeChild(oldPos);
		parentNode.addChild(addPos, draggedNode);

		boolean tempPerformCallbacks = performCallbacks;
		performCallbacks = false;
		try {
			nodeTree.clearSelection();
			model.removeNodeFromParent(draggedTreeNode);
			model.insertNodeInto(draggedTreeNode, parentTreeNode, addPos);
		} finally {
			performCallbacks = tempPerformCallbacks;
		}
		nodeTree.setSelectionPath(new TreePath(draggedTreeNode.getPath()));
	}

	private void validateAnimationField() {
		String animation = animationField.getText();
		boolean valid = editor.animationNames.contains(animation);
		animationField.setBackground(valid ? Color.WHITE : Color.RED);
	}

	private void validateNode(ColoredNode node) {
		String nodeName = node.toString();
		AnimationTree.Node treeNode = editor.animationTree.getNode(nodeName);
		boolean valid;
		if (treeNode.getType() != AnimationTree.Node.Type.ANIMATION) {
			valid = true;
		} else {
			String animation = ((AnimationTree.AnimationNode) treeNode).getAnimation();
			valid = editor.animationNames.contains(animation);
		}
		node.setColor(valid ? Color.BLACK : Color.RED);
		model.nodeChanged(node);
	}

	public void constructCharacterInstance() {
		// no need to construct animations for skeleton modification
		//HashMap<String, Animation> animMap = new HashMap<String, Animation>();
		//for (int i = 0; i < editor.animations.size(); ++i) {
		//	animMap.put(editor.animationNames.get(i), editor.animations.get(i));
		//}
		editor.character = new animation.Character(
				editor.skeleton,
				new AnimationTree(),            // empty tree
				new AnimationStateMachine(),    // empty state machine
				new HashMap<String, Animation>());
		editor.state = editor.character.createCharacterState();
		editor.evaluator.evaluate(editor.state);
		editor.boneColors = new Vector4f[editor.skeleton.getBoneCount()];

		final Vector4f WHITE = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
		for (int i = 0; i < editor.boneColors.length; ++i) {
			editor.boneColors[i] = WHITE;
		}
	}

	private final AnimationEditor editor;

	// PREVIEW FUNCTIONS

	public void selectBoneInPreview(int boneIndex) {}
	public void translateInPreview(Vector2f translation) {}
	public void finishTranslateInPreview() {}
	public void rotateInPreview(float rotation) {}
	public void finishRotateInPreview() {}
	public void scaleInPreview(Vector2f scale) {}
	public void finishScaleInPreview() {}
	public Vector2f getBoneTranslation(int boneIndex) { return null; }
	public float getBoneRotation(int boneIndex) { return 0.0f; }
	public Vector2f getBoneScale(int boneIndex) { return null; }
	public void enableBoneInAnimationInPreview() {}
	public void addKeyframeInPreview() {}
	public void prevKeyframeInPreview(boolean first) {}
	public void nextKeyframeInPreview(boolean last) {}
	public void frameBeginInPreview() {}
}
